/* @flow */

import { hasOwn } from "shared/util";
import { warn, hasSymbol } from "../util/index";
import { defineReactive, toggleObserving } from "../observer/index";

export function initProvide(vm: Component) {
  const provide = vm.$options.provide;
  if (provide) {
    vm._provided = typeof provide === "function" ? provide.call(vm) : provide;
  }
}

export function initInjections(vm: Component) {
  //resolveInject把inject选项中的数据转化成键值对的形式赋给result
  const result = resolveInject(vm.$options.inject, vm);
  if (result) {
    //这个函数内部是把shouldObserve = false，这是为了告诉defineReactive函数仅仅是把键值添加到当前实例上而不需要将其转换成响应式，
    toggleObserving(false);
    //遍历result中的每一对键值，调用defineReactive函数将其添加当前实例上
    Object.keys(result).forEach((key) => {
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== "production") {
        defineReactive(vm, key, result[key], () => {
          warn(
            `Avoid mutating an injected value directly since the changes will be ` +
              `overwritten whenever the provided component re-renders. ` +
              `injection being mutated: "${key}"`,
            vm
          );
        });
      } else {
        defineReactive(vm, key, result[key]);
      }
    });
    toggleObserving(true);
  }
}
/**
 * inject 选项中的每一个数据key都是由其上游父级组件提供的，所以我们应该把每一个数据key从当前组件起，不断的向上游父级组件中查找该数据key对应的值，直到找到为止。
 * 如果在上游所有父级组件中没找到，那么就看在inject 选项是否为该数据key设置了默认值，如果设置了就使用默认值，如果没有设置，则抛出异常。
 * @param {*} inject
 * @param {*} vm
 * @returns
 */
export function resolveInject(inject: any, vm: Component): ?Object {
  if (inject) {
    // inject is :any because flow is not smart enough to figure out cached
    const result = Object.create(null); //首先创建一个空对象result，用来存储inject 选项中的数据key及其对应的值，作为最后的返回结果。
    const keys = hasSymbol ? Reflect.ownKeys(inject) : Object.keys(inject);

    for (let i = 0; i < keys.length; i++) {
      const key = keys[i];
      // #6574 in case the inject object is observed...
      if (key === "__ob__") continue;
      const provideKey = inject[key].from; //拿到每一个key的from属性记作provideKey,provideKey就是上游父级组件提供的源属性
      let source = vm;
      //开启一个while循环，从当前组件起，不断的向上游父级组件的_provided属性中（父级组件使用provide选项注入数据时会将注入的数据存入自己的实例的_provided属性中）查找，
      //直到查找到源属性的对应的值，将其存入result中
      while (source) {
        if (source._provided && hasOwn(source._provided, provideKey)) {
          result[key] = source._provided[provideKey];
          break;
        }
        source = source.$parent;
      }
      if (!source) {
        //如果没有找到，那么就看inject 选项中当前的数据key是否设置了默认值，即是否有default属性，如果有的话，则拿到这个默认值
        if ("default" in inject[key]) {
          const provideDefault = inject[key].default;
          result[key] =
            typeof provideDefault === "function"
              ? provideDefault.call(vm)
              : provideDefault;
        } else if (process.env.NODE_ENV !== "production") {
          warn(`Injection "${key}" not found`, vm);
        }
      }
    }
    return result;
  }
}
